package dto

import (
	cecdsa "crypto/ecdsa"
	"encoding/json"
	"fmt"
	"osiris/logger"
	"osiris/model"
	"osiris/ocrypto/ecdsa"
	"osiris/ocrypto/ed25519"
	"time"

	"github.com/ethereum/go-ethereum/common"
)

// const DataMap中的关键字定义
//
//	@param KEY_PRIVKEY="privKey"
const (
	KEY_PRIVKEY = "privKey"
)

type TxType int

// const 交易类型
const (

	// TX_DEFAULT 默认交易类型
	TX_DEFAULT TxType = iota

	// TX_REGISTER 账户注册
	TX_REGISTER

	// TX_DEPOSIT 代币存入
	TX_DEPOSIT

	// TX_TRANSFORM 代币转移
	TX_TRANSFORM

	// TX_PLEDGE 代币质押
	TX_PLEDGE

	// TX_RELEASE 代币解押
	TX_RELEASE

	// TX_REWARD 出块奖励
	TX_REWARD
)

// TxDto 节点间传递交易用的DTO
type TxDto struct {

	// BaseDto
	BaseDto

	// TxData
	TxData

	// SenderPeerID 交易发送方节点的Peer.ID.String()(其它节点收到消息后该向谁回复)
	SenderPeerID string `json:"senderPeerID"`

	// SenderMultiaddrs 交易发送方节点的ma.Multiaddr.String()（其它节点收到消息后该向谁回复）
	SenderMultiaddrs []string `json:"senderMultiaddrs"`

	// SenderPubKey 交易发送方节点的公钥(16进制字符串)(ed25519+crypto.Marshal)
	SenderPubKey string `json:"senderPubKey"`

	// SenderSignature 交易发送方节点对整个TxDto的hash的签名(edd25519+Base64)
	SenderSignature string `json:"senderSignature"`
}

// Hash 计算TxDto的hash
//
//	@receiver txDto
//	@return error
func (txDto TxDto) Hash() (string, error) {
	txDto.DtoHash = ""
	txDto.SenderSignature = ""
	return SHA256Hash(txDto)
}

// HashAndSign 对TxData计算sha256 hash并用接受交易的节点的ed25516私钥签名
//
//	@receiver txDto
//	@return error
func (txDto *TxDto) HashAndSign() error {
	//计算hash
	sha256HashStr, err1 := txDto.Hash()
	if err1 != nil {
		logger.Error(map[string]interface{}{"[dto] [Hash And Sign TxData] calculate txDto hash": err1})
		return err1
	}
	txDto.DtoHash = sha256HashStr

	//计算签名
	sig, err3 := ed25519.ED25519Sign(ed25519.GetPeerPrivKey(), sha256HashStr)
	if err3 != nil {
		logger.Error(map[string]interface{}{"[dto] [Hash And Sign TxData] sign txDto": err3})
		return err3
	}

	txDto.SenderSignature = sig
	return nil
}

func (txDto TxDto) Verify() bool {
	//计算DtoHash
	dtoHash, err1 := txDto.Hash()
	if err1 != nil {
		logger.Warn(map[string]interface{}{"[dto] [Verify TxDto] calculate hash": err1})
		return false
	}

	//验证DtoHash
	if dtoHash != txDto.DtoHash {
		logger.Warn(map[string]interface{}{"[dto] [Verify TxDto] verify hash": "fail"})
		return false
	}

	//拿公钥
	pubKey, err2 := ed25519.StrToPubKey(txDto.SenderPubKey)
	if err2 != nil {
		logger.Warn(map[string]interface{}{"[dto] [Verify TxDto] str to pubKey": err2})
		return false
	}

	//拿签名
	decodedSignature, err3 := ed25519.StrToED25519Sig(txDto.SenderSignature)
	if err3 != nil {
		logger.Warn(map[string]interface{}{"[dto] [Verify TxDto] decode signature by base64": err3})
		return false
	}

	//验证签名
	valid := ed25519.ED25519Verify(pubKey, txDto.DtoHash, decodedSignature)
	if !valid {
		logger.Warn(map[string]interface{}{"[dto] [Verify Peer] verify sigature": "fail"})
		return false
	}

	return true
}

// TxData 交易数据，客户端向节点提出交易的DTO
type TxData struct {

	// Timestamp 交易创建时的时间戳
	Timestamp int64 `json:"timestamp"`

	// ChainID 属于哪条链
	ChainID int `json:"chainID"`

	// TxType 交易类型
	TxType TxType `json:"txType"`

	// FromAddr 用于输入的账户地址
	FromAddr string `json:"fromAddr"`

	// ToAddr 用于输出的账户地址
	ToAddr string `json:"toAddr"`

	// Amount 转移的代币数量
	Amount int64 `json:"amount"`

	// CoinSince 代币的币龄（=time.Now().Unix()-CoinSince）
	CoinSince int64

	// Gas 消耗的燃气量
	Gas int64 `json:"gas"`

	// GasPrice 愿意提供的燃气价格
	GasPrice int64 `json:"gasPrice"`

	// Nonce 随机数
	Nonce int64 `json:"nonce"`

	// MaxNonce 最大随机数，达到这个值后从0继续(只有账户注册交易时这个值才有效)
	MaxNonce int64 `json:"maxNonce"`

	// TxHash 交易数据的Keccak256 Hash
	TxHash string `json:"txHash"`

	// AccountSignature 输入账户的签名（ECDSA（Keccak256 Hash））
	AccountSignature string `json:"accountSignature"`

	// TxDataMap
	TxDataMap map[string]string `json:"txDatamap"`
}

// Hash
//
//	@receiver txData
//	@return string 由Keccak256 Hash的生成的common.Hash
//	@return error
func (txData TxData) Hash() (common.Hash, error) {
	var commonHash common.Hash
	txData.TxHash = ""
	txData.AccountSignature = ""
	bytes, err := ToBytes(txData)
	if err != nil {
		return commonHash, nil
	}

	commonHash = ecdsa.Keccak256Hash(bytes)
	return commonHash, nil
}

// HashAndSign 打上时间戳后,对TxData计算keccak256 hash并用edcsa签名（理论上这个方法只应出现在客户端，为了方便测试在这里加一个）
//
//	@receiver txData
//	@param privKey ecdsa私钥
//	@param overrideCoinSince 是否用当前时间戳覆盖coinSince
//	@return error
func (txData *TxData) HashAndSign(privKey *cecdsa.PrivateKey, overrideCoinSince bool) error {
	//打时间戳
	txData.Timestamp = time.Now().Unix()
	if overrideCoinSince {
		txData.CoinSince = txData.Timestamp
	}

	//计算hash
	commonHash, err1 := txData.Hash()
	if err1 != nil {
		logger.Error(map[string]interface{}{"[dto] [Hash And Sign TxData] calculate txData hash": err1})
		return err1
	}
	txData.TxHash = ecdsa.Keccak256HashToStr(commonHash)

	//计算签名
	if privKey != nil {
		sig, err3 := ecdsa.ECDSASign(commonHash, privKey)
		if err3 != nil {
			logger.Error(map[string]interface{}{"[dto] [Hash And Sign TxData] sign txData": err3})
			return err3
		}

		txData.AccountSignature = ecdsa.ECDSASigToStr(sig)
	} else {
		txData.AccountSignature = ""
	}

	return nil
}

// Verify 对TxData进行密码学方面的验证：哈希、签名
//
//	@receiver txData
//	@return bool
func (txData TxData) Verify() bool {
	//计算DtoHash
	commonHash, err1 := txData.Hash()
	if err1 != nil {
		logger.Warn(map[string]interface{}{"[dto] [Verify TxData] calculate hash": err1})
		return false
	}

	//验证DtoHash
	if ecdsa.Keccak256HashToStr(commonHash) != txData.TxHash {
		logger.Warn(map[string]interface{}{"[dto] [Verify TxData] verify hash": "fail"})
		return false
	}

	if txData.AccountSignature != "" {
		//拿签名
		sig, err2 := ecdsa.StrToECDSASig(txData.AccountSignature)
		if err2 != nil {
			logger.Error(map[string]interface{}{"[dto] [Verify TxData] str to signature": err2})
			return false
		}

		//验证签名
		valid := ecdsa.ECDSAVerify(commonHash, sig)
		if !valid {
			logger.Warn(map[string]interface{}{"[dto] [Verify TxData] verify sigature": "fail"})
			return false
		}

	}
	return true
}

func (txData TxData) String() string {
	return fmt.Sprintf("timestamp: %d, txType: %d, txHash: %s, fromAddr: %s, toAddr: %s, amount: %d, nonce: %d", txData.Timestamp, txData.TxType, txData.TxHash, txData.FromAddr, txData.ToAddr, txData.Amount, txData.Nonce)
}

func (txData TxData) ToLocalQueuedModel() (*model.LocalQueuedModel, error) {
	txDataMapJson, err := json.Marshal(txData.TxDataMap)
	if err != nil {
		logger.Error(map[string]interface{}{"[dto] [To Local Queued Model] txDataMap to json": err})
		return nil, err
	}

	return &model.LocalQueuedModel{
		Timestamp:        txData.Timestamp,
		ChainID:          txData.ChainID,
		TxType:           int(txData.TxType),
		FromAddr:         txData.FromAddr,
		ToAddr:           txData.ToAddr,
		Amount:           txData.Amount,
		CoinSince:        txData.CoinSince,
		Gas:              txData.Gas,
		GasPrice:         txData.GasPrice,
		Nonce:            txData.Nonce,
		MaxNonce:         txData.MaxNonce,
		TxHash:           txData.TxHash,
		AccountSignature: txData.AccountSignature,
		TxDataMapJson:    string(txDataMapJson),
	}, nil
}

func FromLocalQueuedModel(model model.LocalQueuedModel) (*TxData, error) {
	var txDataMap map[string]string
	err := json.Unmarshal([]byte(model.TxDataMapJson), &txDataMap)
	if err != nil {
		logger.Error(map[string]interface{}{"[dto] [From Local Queued Model] from json to txDataMap": err})
		return nil, err
	}

	return &TxData{
		Timestamp:        model.Timestamp,
		ChainID:          model.ChainID,
		TxType:           TxType(model.TxType),
		FromAddr:         model.FromAddr,
		ToAddr:           model.ToAddr,
		Amount:           model.Amount,
		CoinSince:        model.CoinSince,
		Gas:              model.Gas,
		GasPrice:         model.GasPrice,
		Nonce:            model.Nonce,
		MaxNonce:         model.MaxNonce,
		TxHash:           model.TxHash,
		AccountSignature: model.AccountSignature,
		TxDataMap:        txDataMap,
	}, nil
}
